using System;
using System.Collections;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;
using Pegasus.Log4Net;

namespace Pegasus.Runtime.Serialization.Formatters.Xml
{
	/// <summary>
	/// Provides serialization to and from XML format.
	/// </summary>
	[Obsolete( "Use the XmlFormatter2 class in place of this class", false )]
	public class XmlFormatter : IFormatter
	{
        /// <summary>
        /// 
        /// </summary>
        public static ILog s_logger = LogManager.GetLogger( typeof( XmlFormatter ) );

		/// <summary>
		/// Initializes a new instance of the <see cref="T:XmlFormatter"/> class.
		/// </summary>
		public XmlFormatter()
		{
		}

		/// <summary>
		/// Gets or sets the <see cref="T:System.Runtime.Serialization.SerializationBinder"></see> that performs type lookups during deserialization.
		/// </summary>
		/// <value></value>
		/// <returns>The <see cref="T:System.Runtime.Serialization.SerializationBinder"></see> that performs type lookups during deserialization.</returns>
		public SerializationBinder Binder
		{
			get
			{
				throw new Exception( "The method or operation is not implemented." );
			}
			set
			{
				throw new Exception( "The method or operation is not implemented." );
			}
		}

		/// <summary>
		/// Gets or sets the <see cref="T:System.Runtime.Serialization.StreamingContext"></see> used for serialization and deserialization.
		/// </summary>
		/// <value></value>
		/// <returns>The <see cref="T:System.Runtime.Serialization.StreamingContext"></see> used for serialization and deserialization.</returns>
		public StreamingContext Context
		{
			get
			{
				throw new Exception( "The method or operation is not implemented." );
			}
			set
			{
				throw new Exception( "The method or operation is not implemented." );
			}
		}

		/// <summary>
		/// Gets or sets the <see cref="T:System.Runtime.Serialization.SurrogateSelector"></see> used by the current formatter.
		/// </summary>
		/// <value></value>
		/// <returns>The <see cref="T:System.Runtime.Serialization.SurrogateSelector"></see> used by this formatter.</returns>
		public ISurrogateSelector SurrogateSelector
		{
			get
			{
				throw new Exception( "The method or operation is not implemented." );
			}
			set
			{
				throw new Exception( "The method or operation is not implemented." );
			}
		}

		/// <summary>
		/// Serializes an object, or graph of objects with the given root to the provided stream.
		/// </summary>
		/// <param name="serializationStream">The stream where the formatter puts the serialized data. This stream can reference a variety of backing stores (such as files, network, memory, and so on).</param>
		/// <param name="graph">The object, or root of the object graph, to serialize. All child objects of this root object are automatically serialized.</param>
		public void Serialize( Stream serializationStream, object graph )
		{
			XmlWriterSettings settings = new XmlWriterSettings();
			settings.ConformanceLevel = ConformanceLevel.Document;
			settings.Encoding = UTF8Encoding.UTF8;
			settings.Indent = true;
			settings.OmitXmlDeclaration = true;

			XmlWriter xmlWriter = XmlWriter.Create( serializationStream, settings );

            try
            {
                // Build the security token
                xmlWriter.WriteStartDocument();

                WriteMember( xmlWriter, "ObjectGraph", graph );

                xmlWriter.WriteEndDocument();
                xmlWriter.Flush();
            }
            catch( Exception ex )
            {
                s_logger.Error( "Serialize failed", ex );
                throw;
            }
			finally
			{
				xmlWriter.Close();
			}
		}

		/// <summary>
		/// Deserializes the data on the provided stream and reconstitutes the graph of objects.
		/// </summary>
		/// <param name="serializationStream">The stream that contains the data to deserialize.</param>
		/// <returns>
		/// The top object of the deserialized graph.
		/// </returns>
		public object Deserialize( Stream serializationStream )
		{
			// TODO: Use a forward reader, it should be faster...

			XmlDocument xmlDoc = new XmlDocument();
			xmlDoc.Load( serializationStream );

			foreach( XmlNode node in xmlDoc.ChildNodes )
			{
				if( node.Name == "ObjectGraph" )
				{
					return ReadMember( node );
				}
			}

			return null;
		}

		/// <summary>
		/// Writes the member.
		/// </summary>
		/// <param name="xmlWriter">The XML writer.</param>
		/// <param name="memberName">Name of the member.</param>
		/// <param name="data">The data.</param>
		private void WriteMember( XmlWriter xmlWriter, string memberName, object data )
		{
			if( data == null )
			{
				WriteObjectRef( xmlWriter, data, memberName, typeof( object ) );
			}
			else
			{
				Type type = data.GetType();
				if( type == typeof( bool ) ||
					type == typeof( char ) ||
					type == typeof( sbyte ) ||
					type == typeof( byte ) ||
					type == typeof( short ) ||
					type == typeof( ushort ) ||
					type == typeof( int ) ||
					type == typeof( uint ) ||
					type == typeof( long ) ||
					type == typeof( ulong ) ||
					type == typeof( float ) ||
					type == typeof( double ) ||
					type == typeof( decimal ) )
				{
					WriteBasicType( xmlWriter, data, memberName );
				}
				else if( type == typeof( DateTime ) )
				{
					WriteBasicType( xmlWriter, Convert.ToDateTime( data, CultureInfo.InvariantCulture ), memberName );
				}
				else if( type.IsArray )
				{
					this.WriteArray( xmlWriter, data, memberName, type );
				}
				else if( type.IsValueType )
				{
					this.WriteValueType( xmlWriter, data, memberName, type );
				}
				else
				{
					this.WriteObjectRef( xmlWriter, data, memberName, type );
				}
			}
		}

		/// <summary>
		/// Writes the type of the basic.
		/// </summary>
		/// <param name="xmlWriter">The XML writer.</param>
		/// <param name="val">The val.</param>
		/// <param name="name">The name.</param>
		private void WriteBasicType( XmlWriter xmlWriter, object val, string name )
		{
			xmlWriter.WriteStartElement( XmlConvert.EncodeName( name ) );
			xmlWriter.WriteAttributeString( "Type", val.GetType().AssemblyQualifiedName );

			xmlWriter.WriteValue( val.ToString() );

			xmlWriter.WriteEndElement();
		}

		/// <summary>
		/// Writes the array.
		/// </summary>
		/// <param name="xmlWriter">The XML writer.</param>
		/// <param name="obj">The obj.</param>
		/// <param name="name">The name.</param>
		/// <param name="memberType">Type of the member.</param>
		private void WriteArray( XmlWriter xmlWriter, object obj, string name, Type memberType )
		{
			xmlWriter.WriteStartElement(  XmlConvert.EncodeName( name ) );
			xmlWriter.WriteAttributeString( "Type", memberType.AssemblyQualifiedName );

			// Write the elements of the array
			Array array = (Array) obj;
			foreach( object element in array )
			{
				WriteMember( xmlWriter, name, element );
			}

			xmlWriter.WriteEndElement();
		}

		/// <summary>
		/// Writes the type of the value.
		/// </summary>
		/// <param name="xmlWriter">The XML writer.</param>
		/// <param name="obj">The obj.</param>
		/// <param name="name">The name.</param>
		/// <param name="memberType">Type of the member.</param>
		private void WriteValueType( XmlWriter xmlWriter, object obj, string name, Type memberType )
		{
			xmlWriter.WriteStartElement( XmlConvert.EncodeName( name ) );
			xmlWriter.WriteAttributeString( "Type", memberType.AssemblyQualifiedName );

			FieldInfo[] fields = memberType.GetFields( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
			object[] membersData = FormatterServices.GetObjectData( obj, fields );

			int count = fields.Length;
			for( int x = 0; x < count; x++ )
			{
				WriteMember( xmlWriter, fields[ x ].Name, membersData[ x ] );
			}

			xmlWriter.WriteEndElement();
		}

		/// <summary>
		/// Writes the object ref.
		/// </summary>
		/// <param name="xmlWriter">The XML writer.</param>
		/// <param name="obj">The obj.</param>
		/// <param name="name">The name.</param>
		/// <param name="memberType">Type of the member.</param>
		private void WriteObjectRef( XmlWriter xmlWriter, object obj, string name, Type memberType )
		{
			xmlWriter.WriteStartElement( XmlConvert.EncodeName( name ) );
			xmlWriter.WriteAttributeString( "Type", memberType.AssemblyQualifiedName );

			if( obj != null )
			{
				if( memberType != typeof( string ) )
				{
					MemberInfo[] members = FormatterServices.GetSerializableMembers( memberType );
					object[] membersData = FormatterServices.GetObjectData( obj, members );

					int count = members.Length;
					for( int x = 0; x < count; x++ )
					{
						WriteMember( xmlWriter, members[ x ].Name, membersData[ x ] );
					}
				}
				else
				{
					xmlWriter.WriteValue( (string) obj );
				}
			}
			else
			{
				xmlWriter.WriteValue( "NULL" );
			}

			xmlWriter.WriteEndElement();
		}

		/// <summary>
		/// Reads the member.
		/// </summary>
		/// <param name="node">The node.</param>
		/// <returns></returns>
		private object ReadMember( XmlNode node )
		{
			object ret = null;

			XmlAttribute attrib = node.Attributes[ "Type" ];
			if( attrib == null )
			{
				throw new XmlException( string.Format( "Missing 'Type' attribute on node {0}", node.Name ) );
			}

			string qualifiedAssemblyName = attrib.Value;

			Type memberType = Type.GetType( qualifiedAssemblyName );
			if( memberType == null )
			{
				throw new XmlException( string.Format( "Unknown 'Type' attribute {0} on node {1}", qualifiedAssemblyName, node.Name ) );
			}

			if( memberType == typeof( bool ) )
			{
				ret = Convert.ToBoolean( node.InnerText );
			}
			else if( memberType == typeof( char ) )
			{
				ret = Convert.ToChar( node.InnerText );
			}
			else if( memberType == typeof( string ) )
			{
				ret = node.InnerText;
			}
			else if( memberType == typeof( sbyte ) )
			{
				ret = Convert.ToSByte( node.InnerText );
			}
			else if( memberType == typeof( byte ) )
			{
				ret = Convert.ToByte( node.InnerText );
			}
			else if( memberType == typeof( short ) )
			{
				ret = Convert.ToInt16( node.InnerText );
			}
			else if( memberType == typeof( ushort ) )
			{
				ret = Convert.ToUInt16( node.InnerText );
			}
			else if( memberType == typeof( int ) )
			{
				ret = Convert.ToInt32( node.InnerText );
			}
			else if( memberType == typeof( uint ) )
			{
				ret = Convert.ToUInt32( node.InnerText );
			}
			else if( memberType == typeof( long ) )
			{
				ret = Convert.ToInt64( node.InnerText );
			}
			else if( memberType == typeof( ulong ) )
			{
				ret = Convert.ToUInt64( node.InnerText );
			}
			else if( memberType == typeof( float ) )
			{
				ret = Convert.ToSingle( node.InnerText );
			}
			else if( memberType == typeof( double ) )
			{
				ret = Convert.ToDouble( node.InnerText );
			}
			else if( memberType == typeof( decimal ) )
			{
				ret = Convert.ToDecimal( node.InnerText );
			}
			else if( memberType == typeof( DateTime ) )
			{
				ret = Convert.ToDateTime( node.InnerText, CultureInfo.InvariantCulture );
			}
			else if( memberType.IsArray )
			{
				ReadArray( node, ref ret, memberType );
			}
			else if( memberType.IsValueType )
			{
				ReadValueType( node, ref ret, memberType );
			}
			else
			{
				ReadObjectRef( node, ref ret, memberType );
			}

			return ret;
		}

		/// <summary>
		/// Reads the array.
		/// </summary>
		/// <param name="node">The node.</param>
		/// <param name="theObject">The object.</param>
		/// <param name="memberType">Type of the member.</param>
		private void ReadArray( XmlNode node, ref object theObject, Type memberType )
		{
			int count = node.ChildNodes.Count;

			// Create the array object
			Array array = Array.CreateInstance( memberType.GetElementType(), count );
			
			// Read each the element of the array
			for( int x = 0; x < count; x++ )
			{
				array.SetValue( ReadMember( node.ChildNodes[ x ] ), x );
			}

			theObject = array;
		}

		/// <summary>
		/// Reads the type of the value.
		/// </summary>
		/// <param name="node">The node.</param>
		/// <param name="theObject">The object.</param>
		/// <param name="memberType">Type of the member.</param>
		private void ReadValueType( XmlNode node, ref object theObject, Type memberType )
		{
			theObject = FormatterServices.GetSafeUninitializedObject( memberType );
			FieldInfo[] fields = memberType.GetFields( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );

			int count = fields.Length;
			object[] data = new object[ count ];

			for( int x = 0; x < count; x++ )
			{
				string name = fields[ x ].Name;
				XmlNode childNode = node.SelectSingleNode( XmlConvert.EncodeName( name ) );
				if( childNode != null )
				{
					data[ x ] = ReadMember( childNode );
				}
			}

			theObject = FormatterServices.PopulateObjectMembers( theObject, fields, data );
		}

		/// <summary>
		/// Reads the object ref.
		/// </summary>
		/// <param name="node">The node.</param>
		/// <param name="theObject">The object.</param>
		/// <param name="memberType">Type of the member.</param>
		private void ReadObjectRef( XmlNode node, ref object theObject, Type memberType )
		{

			if( node.InnerText != "NULL" )
			{
				if( memberType == typeof( string ) )
				{
					theObject = node.InnerText;
				}
				else
				{
					theObject = FormatterServices.GetSafeUninitializedObject( memberType );
					MemberInfo[] members = FormatterServices.GetSerializableMembers( memberType );

					int count = members.Length;
					object[] data = new object[ count ];

					for( int x = 0; x < count; x++ )
					{
						string name = members[ x ].Name;
						XmlNode childNode = node.SelectSingleNode( XmlConvert.EncodeName( name ) );
						if( childNode != null )
						{
							data[ x ] = ReadMember( childNode );
						}
					}

					theObject = FormatterServices.PopulateObjectMembers( theObject, members, data );
				}
			}
			else
			{
				theObject = null;
			}
		}
	}
}
